#include "Url.hpp"

const char Url::hex_chars[16] = {
	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
	'A', 'B', 'C', 'D', 'E', 'F'
}; 

size_t Url::_urlEncodeOverhead(const char *str, size_t len, bool slash) {
	size_t overhead = 0; 
	for (size_t i = 0; i < len; ++i) {
		if (!isalpha(str[i]) && !isdigit(str[i]) && !(
			str[i] == '$' || str[i] == '-' || str[i] == '_' || str[i] == '.'  || 
			str[i] == '+' || str[i] == '!' || str[i] == '*' || str[i] == '\'' || 
			str[i] == '(' || str[i] == ')' || str[i] == ',' || (slash && str[i] == '/')
		))
			overhead += 2; 
	}
	return overhead; 
}

size_t Url::_urlEncodeUnderhead(const char *str, size_t len) {
	size_t underhead = 0; 
	for (size_t i = 0; i < len; ++i) {
		if (str[i] == '%' && i + 2 < len && isxdigit(str[i + 1]) && isxdigit(str[i + 2]))
			underhead += 2; 
	}
	return underhead; 
}

size_t Url::_urlEncodeOverhead(const string &str, bool slash) {
	unsigned int overhead = 0; 
	for (auto x: str) {
		if (!isalpha(x) && !isdigit(x) && !(
			x == '$' || x == '-' || x == '_' || x == '.'  || 
			x == '+' || x == '!' || x == '*' || x == '\'' || 
			x == '(' || x == ')' || x == ',' || (slash && x == '/')
		))
			overhead += 2; 
	}
	return overhead; 
}

size_t Url::_urlEncodeUnderhead(const string &str) {
	size_t underhead = 0; 
	for (size_t i = 0; i < str.length(); ++i) {
		if (str[i] == '%' && i + 2 < str.length() && isxdigit(str[i + 1]) && isxdigit(str[i + 2]))
			underhead += 2; 
	}
	return underhead; 
}

void Url::appendToUrl(string &url, const string &part, bool slash) {
	size_t reserve = _urlEncodeOverhead(part); 
	if (reserve)
		url.reserve(url.length() + reserve); 
	for (auto x: part) {
		if (x == ' ')
			url += '+'; 
		else if (!isalpha(x) && !isdigit(x) && !(
			x == '$' || x == '-' || x == '_' || x == '.'  || 
			x == '+' || x == '!' || x == '*' || x == '\'' || 
			x == '(' || x == ')' || x == ',' || (slash && x == '/')
		)) {
			url.append(1, '%').append(1, hex_chars[x >> 4 & 0xF]).append(1, hex_chars[x & 0xF]); 
		} else
			url += x; 
	}
}

void Url::appendToUrl(string &url, const char *part, size_t len, bool slash) {
	size_t reserve = _urlEncodeOverhead(part, len, slash); 
	if (reserve)
		url.reserve(url.length() + reserve); 
	for (size_t i = 0; i < len; ++i) {
		if (part[i] == ' ')
			url += '+'; 
		else if (!isalpha(part[i]) && !isdigit(part[i]) && !(
			part[i] == '$' || part[i] == '-' || part[i] == '_' || part[i] == '.'  || 
			part[i] == '+' || part[i] == '!' || part[i] == '*' || part[i] == '\'' || 
			part[i] == '(' || part[i] == ')' || part[i] == ',' || (slash && part[i] == '/')
		)) {
			url.append(1, '%').append(1, hex_chars[part[i] >> 4 & 0xF]).append(1, hex_chars[part[i] & 0xF]); 
		} else
			url += part[i]; 
	}
}

char *Url::encode(char *str, size_t length, size_t *new_length, bool *need_to_free) {
	size_t overhead = _urlEncodeOverhead(str); 
	char *new_buffer = overhead ? (char *)malloc(overhead + length + 1) : str; 
	char *p_new_buffer = new_buffer; 
	if (need_to_free)
		*need_to_free = (overhead > 0); 
	
	for (size_t i = 0; i < length; ++i) {
		if (str[i] == ' ')
			*p_new_buffer++ = '+'; 
		else if (!isalpha(str[i]) && !isdigit(str[i]) && !(
			str[i] == '$' || str[i] == '-' || str[i] == '_' || str[i] == '.'  || 
			str[i] == '+' || str[i] == '!' || str[i] == '*' || str[i] == '\'' || 
			str[i] == '(' || str[i] == ')' || str[i] == ','
		)) {
			*p_new_buffer++ = '%'; 
			*p_new_buffer++ = hex_chars[str[i] >> 4 & 0xF]; 
			*p_new_buffer++ = hex_chars[str[i] & 0xF]; 
		} else
			*p_new_buffer++ = str[i]; 
	}
	*p_new_buffer = 0; 
	*new_length = overhead + length; 
	
	return new_buffer; 
}

string &Url::encode(string &str) {
	string new_buffer; 
	new_buffer.reserve(str.length() + _urlEncodeOverhead(str)); 
	for (auto x: str) {
		if (x == ' ')
			new_buffer += '+'; 
		else if (!isalpha(x) && !isdigit(x) && !(
			x == '$' || x == '-' || x == '_' || x == '.'  || 
			x == '+' || x == '!' || x == '*' || x == '\'' || 
			x == '(' || x == ')' || x == ','
		)) {
			new_buffer += '%'; 
			new_buffer += hex_chars[x >> 4 & 0xF]; 
			new_buffer += hex_chars[x & 0xF]; 
		} else
			new_buffer += x; 
	}
	str.swap(new_buffer); 
	return str; 
}

string Url::encodeDup(const string &str) {
	string new_buffer; 
	new_buffer.reserve(str.length() + _urlEncodeOverhead(str)); 
	for (auto x: str) {
		if (x == ' ')
			new_buffer += '+'; 
		else if (!isalpha(x) && !isdigit(x) && !(
			x == '$' || x == '-' || x == '_' || x == '.'  || 
			x == '+' || x == '!' || x == '*' || x == '\'' || 
			x == '(' || x == ')' || x == ','
		)) {
			new_buffer += '%'; 
			new_buffer += hex_chars[x >> 4 & 0xF]; 
			new_buffer += hex_chars[x & 0xF]; 
		} else
			new_buffer += x; 
	}
	return new_buffer; 
}

char *Url::decode(char *str, size_t len, size_t *new_len, bool dup, bool calc_new) {
	char *p_dst, *dst; 
	*new_len = calc_new ? len - _urlEncodeUnderhead(str, len) : len; 
	dst = dup ? (char *)malloc(*new_len + 1) : str; 
	p_dst = dst; 
	
	if (calc_new && *new_len == len) {
		if (dup)
			memcpy(dst, str, len); 
		return dst; 
	}
	
	for (size_t i = 0; i < len; ++i) {
		if (str[i] == '%' && i + 2 < len && isxdigit(str[i + 1]) && isxdigit(str[i + 2])) {
			*p_dst++ = (unsigned char)(HEXDEC(str[i + 1]) << 4) + HEXDEC(str[i + 2]); 
			i += 2; 
		} else
			*p_dst++ = str[i]; 
	}
	*p_dst = 0; 
	
	return dst; 
}

string &Url::decode(string &str) {
	size_t j = 0; 
	for (size_t i = 0; i < str.length(); ++i) {
		if (str[i] == '%' && i + 2 < str.length() && isxdigit(str[i + 1]) && isxdigit(str[i + 2])) {
			str[j++] = (unsigned char)(HEXDEC(str[i + 1]) << 4) + HEXDEC(str[i + 2]); 
			i += 2; 
		} else
			str[j++] = str[i]; 
	}
	str.resize(j); 
	return str; 
}

string Url::decodeDup(const string &str, bool calc_new) {
	string out; 
	out.reserve(str.length() - (calc_new ? _urlEncodeUnderhead(str) : 0)); 
	for (size_t i = 0; i < str.length(); ++i) {
		if (str[i] == '%' && i + 2 < str.length() && isxdigit(str[i + 1]) && isxdigit(str[i + 2])) {
			out += (unsigned char)(HEXDEC(str[i + 1]) << 4) + HEXDEC(str[i + 2]); 
			i += 2; 
		} else
			out += str[i]; 
	}
	return out; 
}

void Url::parse(const char *url, unsigned int len) {
	const char *pos; 
	char *p_val, *ps_val; 
	char name[MAX_QUERY_PART + 1], value[MAX_QUERY_PART + 1]; 
	char port[MAX_PORT_LENGTH + 1]; 
	
	bool is_value = false; 
	unsigned char state = S_SCHEME; 
	
	_initStruct(); 
	for (unsigned int i = 0; i < len && state != S_END; ) {
		switch (state) {
			case S_SCHEME: 
				if (i + 1 < len && url[i] == '/' && url[i + 1] == '/') {
					i += 2; 
					uri.scheme_length = sizeof(RELATIVE_SCHEME) - 1; 
					strcpy(uri.scheme, RELATIVE_SCHEME); 
					state = strchr(url + i, '@') ? S_USER : S_HOST; 
				} else {
					p_val = ps_val = (char *)uri.scheme; 
					while (i < len && p_val - ps_val < MAX_SCHEME_LENGTH && VALIDATE_SCHEME(url[i]))
						*p_val++ = url[i++]; 
					*p_val = 0; 
					if (p_val - ps_val < MIN_SCHEME_LENGTH || (i < len && url[i++] != ':')) {
						p_val = ps_val; 
						*p_val = 0; 
						uri.scheme_length = 0; 
						i = 0; 
						state = S_PATH; 
					} else {
						uri.scheme_length = p_val - ps_val; 
						if (i + 1 < len && url[i] == '/' && url[i + 1] == '/') {
							i += 2; 
							state = strchr(url + i, '@') ? S_USER : S_HOST; 
						} else
							state = S_PATH; 
					}
				}
			break; 
			
			case S_USER: 
				p_val = ps_val = (char *)uri.user; 
				while (i < len && p_val - ps_val < MAX_USER_LENGTH && VALIDATE_AUTH(url[i])) {
					if (url[i] == '%' && i + 2 < len && isxdigit(url[i + 1]) && isxdigit(url[i + 2])) {
						*p_val++ = (unsigned char)(HEXDEC(url[i + 1]) << 4) + HEXDEC(url[i + 2]); 
						i += 3; 
					} else
						*p_val++ = url[i++]; 
				}
				*p_val = 0; 
				
				uri.user_length = p_val - ps_val; 
				if (url[i] == '@' && uri.user_length >= MIN_USER_LENGTH)
					state = S_HOST; 
				else if (url[i] == ':' && uri.user_length >= MIN_USER_LENGTH)
					state = S_PASS; 
				else {
					i = strchr(url + i, '@') - url; 
					uri.user_length = 0; 
					uri.user[0] = '\0'; 
					state = S_HOST; 
				}
				++i; 
			break; 
			
			case S_PASS: 
				p_val = ps_val = (char *)uri.pass; 
				while (i < len && p_val - ps_val < MAX_USER_LENGTH && VALIDATE_AUTH(url[i])) {
					if (url[i] == '%' && i + 2 < len && isxdigit(url[i + 1]) && isxdigit(url[i + 2])) {
						*p_val++ = (unsigned char)(HEXDEC(url[i + 1]) << 4) + HEXDEC(url[i + 2]); 
						i += 3; 
					} else
						*p_val++ = url[i++]; 
				}
				*p_val = 0; 
				
				uri.pass_length = p_val - ps_val; 
				if (url[i] != '@' || uri.pass_length < MIN_USER_LENGTH) {
					i = strchr(url + i, '@') - url; 
					uri.pass_length = 0; 
					uri.pass[0] = '\0'; 
				}
				++i; 
				state = S_HOST; 
			break; 
			
			case S_HOST: 
				p_val = ps_val = (char *)uri.host; 
				while (i < len && p_val - ps_val < MAX_HOST_LENGTH && VALIDATE_HOST(url[i])) {
					if (url[i] == '%' && i + 2 < len && isxdigit(url[i + 1]) && isxdigit(url[i + 2])) {
						*p_val++ = (unsigned char)(HEXDEC(url[i + 1]) << 4) + HEXDEC(url[i + 2]); 
						i += 3; 
					} else
						*p_val++ = url[i++]; 
				}
				*p_val = 0; 
				
				uri.host_length = p_val - ps_val; 
				if (url[i] != ':' && url[i] != '?' && url[i] != '/' && url[i] != '#') {
					pos = strpbrk(url + i, ":/?#"); 
					if (pos)
						i = pos - url; 
				}
				
				state = S_END; 
				if (i < len) {
					state = S_PATH; 
					if (uri.host_length >= MIN_HOST_LENGTH) {
						if (url[i] == ':')
							state = S_PORT; 
						else if (url[i] == '#')
							state = S_FRAGMENT; 
						else if (url[i] == '?')
							state = S_QUERY; 
					}
					if (state != S_PATH)
						++i; 
				}
			break; 
			
			case S_PORT: 
				p_val = ps_val = (char *)port; 
				while (i < len && p_val - ps_val < MAX_HOST_LENGTH && isdigit(url[i]))
					*p_val++ = url[i++]; 
				*p_val = 0; 
				
				uri.port = p_val - ps_val < MIN_PORT_LENGTH || atoi(port) > (unsigned short)(-1) ? 0 : atoi(port); 
				state = S_END; 
				if (i < len) {
					if (url[i] != '?' && url[i] != '/' && url[i] != '#') {
						pos = strpbrk(url + i, "/?#"); 
						if (pos)
							i = pos - url; 
					}
					
					if (url[i] == ':')
						state = S_PORT; 
					else if (url[i] == '#')
						state = S_FRAGMENT; 
					else if (url[i] == '?')
						state = S_QUERY; 
					else
						state = S_PATH; 
					if (state != S_PATH)
						++i; 
				}
			break; 
			
			case S_PATH: 
				p_val = ps_val = (char *)uri.path; 
				while (i < len && p_val - ps_val < MAX_PATH_LENGTH && VALIDATE_PATH(url[i])) {
					if (url[i] == '%' && i + 2 < len && isxdigit(url[i + 1]) && isxdigit(url[i + 2])) {
						*p_val++ = (unsigned char)(HEXDEC(url[i + 1]) << 4) + HEXDEC(url[i + 2]); 
						i += 3; 
					} else
						*p_val++ = url[i++]; 
				}
				
				*p_val = 0; 
				uri.path_length = p_val - ps_val; 
				state = S_END; 
				
				if (i < len) {
					if (url[i] != '?' && url[i] != '#') {
						pos = strpbrk(url + i, "?#"); 
						if (pos)
							i = pos - url; 
					}
					state = url[i] == '#' ? S_FRAGMENT : S_QUERY; 
					++i; 
				}
			break; 
			
			case S_QUERY: 
				p_val = ps_val = (char *)name; 
				is_value = false; 
				
				name[0] = value[0] = 0; 
				while (true) {
					if (!url[i]) {
						if (i < len) {
							++i; 
							continue; 
						}
						break; 
					}
					
					if (!is_value && url[i] == '=') {
						*p_val = 0; 
						p_val = ps_val = (char *)value; 
						i++; 
					} else if (IS_QUERY_SEP(url[i]) || i == len - 1 || !VALIDATE_QUERY(url[i])) {
						if (i == len - 1 && !IS_QUERY_SEP(url[i]) && VALIDATE_QUERY(url[i])) {
							*p_val++ = url[i++]; 
						} else if(IS_QUERY_SEP(url[i])) {
							if (url[i] == '&' && i + 4 < len && strncasecmp(url + i + 1, "amp;", 4) == 0)
								i += 4; 
							++i; 
						}
						*p_val = 0; 
						p_val = ps_val = (char *)name; 
						_addQueryKey(uri.query, name, value); 
						name[0] = value[0] = 0; 
						is_value = false; 
						if (!VALIDATE_QUERY(url[i]))
							break; 
					} else {
						while (i < len && p_val - ps_val < MAX_QUERY_PART && url[i] != '=' && !IS_QUERY_SEP(url[i]) && VALIDATE_QUERY(url[i])) {
							if (url[i] == '%' && i + 2 < len && isxdigit(url[i + 1]) && isxdigit(url[i + 2])) {
								*p_val++ = (unsigned char)(HEXDEC(url[i + 1]) << 4) + HEXDEC(url[i + 2]); 
								i += 3; 
							} else
								*p_val++ = url[i++]; 
						}
					}
					if (i >= len) {
						*p_val = 0; 
						if (p_val - ps_val > MIN_QUERY_PART)
							_addQueryKey(uri.query, name, value); 
						break; 
					}
				}
				
				--i; 
				state = S_END; 
				if (i < len) {
					if (url[i] != '#') {
						pos = strchr(url + i, '#'); 
						if (pos)
							i = pos - url; 
					}
					if (url[i] == '#')
						state = S_FRAGMENT; 
				}
				++i; 
			break; 
			
			case S_FRAGMENT: 
				p_val = ps_val = (char *)uri.fragment; 
				while (i < len && p_val - ps_val < MAX_FRAGMENT_LENGTH && VALIDATE_FRAG(url[i])) {
					if (url[i] == '%' && i + 2 < len && isxdigit(url[i + 1]) && isxdigit(url[i + 2])) {
						*p_val++ = (unsigned char)(HEXDEC(url[i + 1]) << 4) + HEXDEC(url[i + 2]); 
						i += 3; 
					} else
						*p_val++ = url[i++]; 
				}
				*p_val = 0; 
				uri.fragment_length = p_val - ps_val; 
				state = S_END; 
			break; 
		}
	}
}

string Url::toString(const char *separator) {
	string url; 
	if (uri.scheme_length)
		url.append(uri.scheme, uri.scheme_length).append(":"); 
	if (uri.host_length || uri.user_length || uri.pass_length || uri.port) {
		url += "//"; 
		if (uri.user_length) {
			appendToUrl(url, uri.user, uri.user_length); 
			if (uri.pass_length) {
				url.append(":"); 
				appendToUrl(url, uri.pass, uri.pass_length); 
			}
			url += "@"; 
		}
		if (uri.host_length)
			appendToUrl(url, uri.host, uri.host_length); 
		if (uri.port)
			url.append(":").append(std::to_string(uri.port)); 
	}
	if (uri.path_length)
		appendToUrl(url, uri.path, uri.path_length, true); 
	if (uri.query.size()) {
		url += "?"; 
		bool first = true; 
		for (auto x: uri.query) {
			if (!first)
				url += separator; 
			else
				first = false; 
			appendToUrl(url, x.name); 
			if (x.value.length()) {
				url.append("="); 
				appendToUrl(url, x.value); 
			}
		}
	}
	if (uri.fragment_length) {
		url.append("#"); 
		appendToUrl(url, uri.fragment, uri.fragment_length); 
	}
	return url; 
}

#if 0
int main() {
	Url url; 
	string link; 
	
	link = "http://spaces.ru:80/friends/?r=friends/index&r=1&link_id=1547422&name=Zhumarin&sort=2"; 
	cout << "URL: " << link << "\n"; 
	url.parse(link.c_str(), link.length()); 
	
	url["link_id2"] = "200"; 
	cout << url.toString("&") << "\n"; 
	
	cout << url["link_id"] << "\n"; 
	return 0; 
}
#endif
